Skip to content

Conversation

@LaurentHuzard
Copy link
Contributor

@LaurentHuzard LaurentHuzard commented Jan 14, 2025

Q A
Branch? main
Tickets -
License MIT
Doc PR api-platform/docs/pull/2211

To ensure compliance with the LDP specification (https://www.w3.org/TR/ldp/):

  • Added the "Accept-Post" header containing the list of supported Post formats.
  • Added the "Allow" header with values based on the allowed operations on the queried resources.

@LaurentHuzard LaurentHuzard marked this pull request as draft January 14, 2025 15:41
When I add "Content-Type" header equal to "application/ld+json"
And I send a "GET" request to "/dummy_get_post_delete_operations"
Then the header "Accept-Post" should be equal to "text/turtle, application/ld+json"
And the header "Allow" should be equal to "OPTIONS, HEAD, GET, POST, DELETE"
Copy link
Member

@dunglas dunglas Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't the value be different for item and collection URLs?

Also, Allow-Post should only be defined for URLs where POST is enabled (usually collections, but not always).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You’re right, I overlooked that. I’ll make the changes and push them soon.

$headers['Accept-Patch'] = $acceptPatch;
}

$headers['Accept-Post'] = 'text/turtle, application/ld+json';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This value cannot be hardcoded. For instance Turtle is not supported natively by API Platform and JSON-LD may or may not depending on the config.

You should inject the enabled formats here. You can retrieve this from metadata. Listing all allowed media types (not only Turtle and JSON-LD) is probably OK (and a best practice).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds much better to list all the allowed media types. I'll take care of it.


private function getAllowedMethods(?string $resourceClass): string
{
$allowedMethods = self::DEFAULT_ALLOWED_METHOD;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should filter here the operations with a different URI Template than the one of the current operation to fix the issue described in my 1st comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, good point—I’ll sort this out.

$headers['Accept-Patch'] = $acceptPatch;
}

if (!$exception) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid returning incorrect values—such as in the case of a 401 error when requesting a resource—I added this condition, although I’m not entirely sure it’s the best approach. The issue arises because, in the case of a 401 error, the allowed methods and the currentUriTemplate correspond to the error itself rather than the requested resource.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approach looks good to me.

}
}

return array_unique($allowedMethods);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's necessary because it's not possible to register two different routes for the same URL and the same method.

It would remove this as we are on the hot path.

To be honest, I would inline this code and remove this method. This will also allow you to set a variable (instead of running an O(n) operation) to check if POST is defined or not for Accept-Post, and this would remove a function call.

(Yes, these are mostly a micro optimization, but this also simplify the code IMHO).

private function getAllowedMethods(?string $resourceClass, ?string $currentUriTemplate): array
{
$allowedMethods = self::DEFAULT_ALLOWED_METHOD;
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass)) {
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver?->isResourceClass($resourceClass)) {

{
$allowedMethods = self::DEFAULT_ALLOWED_METHOD;
if (null !== $currentUriTemplate && null !== $resourceClass && $this->resourceClassResolver->isResourceClass($resourceClass)) {
$resourceMetadataCollection = $this->resourceCollectionMetadataFactory ? $this->resourceCollectionMetadataFactory->create($resourceClass) : new ResourceMetadataCollection($resourceClass);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
$resourceMetadataCollection = $this->resourceCollectionMetadataFactory ? $this->resourceCollectionMetadataFactory->create($resourceClass) : new ResourceMetadataCollection($resourceClass);
$resourceMetadataCollection = $this->resourceCollectionMetadataFactory ? $this->resourceCollectionMetadataFactory->create($resourceClass) : new ResourceMetadataCollection($resourceClass);

Better not set the extra headers at all if the factory is missing.

It's an optional feature after all.

}
$headers['Allow'] = implode(', ', $allowedMethods);

if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) {
if ($isPostAllowed && \is_array($outputFormats = $operation->getOutputFormats()) && [] !== $outputFormats) {

private ?IriConverterInterface $iriConverter = null,
private readonly ?ResourceClassResolverInterface $resourceClassResolver = null,
private readonly ?OperationMetadataFactoryInterface $operationMetadataFactory = null,
private readonly ?ResourceMetadataCollectionFactoryInterface $resourceCollectionMetadataFactory = null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could probably create an LinkedDataPlatformProcessor?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion! A LinkedDataPlatformProcessor sounds like a good idea, I'll consider implementing it.

$headers['Accept-Patch'] = $acceptPatch;
}

if (!$exception) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if ($operation instanceof ApiPlatform\Metadata\Error)

foreach ($resource->getOperations() as $resourceOperation) {
if ($resourceOperation->getUriTemplate() === $currentUriTemplate) {
$allowedMethods[] = $operationMethod = $resourceOperation->getMethod();
$isPostAllowed = $isPostAllowed || ('POST' === $operationMethod);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could collect outputFormats here directly? The operation below may not be the one where the formats are defined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Thanks.

}
$headers['Allow'] = implode(', ', $allowedMethods);

if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ($isPostAllowed && \is_array($outputFormats = ($outputFormats = $operation->getOutputFormats())) && [] !== $outputFormats) {
if ($isPostAllowed && ($outputFormats = $operation->getOutputFormats())) {

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RespondProcessorTest extends TestCase
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a RespondProcessorTest already exists it must be in tests forgot to move it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, i will merge them.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existing RespondProcessorTest use the old way of doing test with prophecy instead of PHPunit mock system, should i refactor it ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as you wish ^^

<argument type="service" id="api_platform.iri_converter" />
<argument type="service" id="api_platform.resource_class_resolver" />
<argument type="service" id="api_platform.metadata.operation.metadata_factory" />
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on-invalid=ignore

@LaurentHuzard LaurentHuzard force-pushed the feat/header-for-ldp-spec branch from 6619485 to d072fd5 Compare March 17, 2025 04:39
@LaurentHuzard LaurentHuzard force-pushed the feat/header-for-ldp-spec branch from d072fd5 to b6403aa Compare June 5, 2025 19:03
Copy link
Member

@soyuka soyuka left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice a few small changes, also you should consider rebasing to be sure that the base ci is green

@stale
Copy link

stale bot commented Aug 17, 2025

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Aug 17, 2025
@stale stale bot closed this Aug 24, 2025
@soyuka soyuka reopened this Aug 24, 2025
@soyuka soyuka added enhancement and removed stale labels Aug 24, 2025
@LaurentHuzard LaurentHuzard force-pushed the feat/header-for-ldp-spec branch 2 times, most recently from f5fd9bf to 64a0bfe Compare September 22, 2025 08:10
@LaurentHuzard LaurentHuzard force-pushed the feat/header-for-ldp-spec branch 2 times, most recently from ef08582 to 55fb914 Compare January 13, 2026 20:43
@LaurentHuzard LaurentHuzard marked this pull request as ready for review January 15, 2026 06:25
@soyuka soyuka requested a review from Copilot January 16, 2026 15:59
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds HTTP headers to comply with the Linked Data Platform (LDP) specification. The implementation introduces the LinkedDataPlatformProcessor which adds Accept-Post and Allow headers to API responses based on the available operations for each resource.

Changes:

  • Added LinkedDataPlatformProcessor to set Accept-Post header (listing supported POST formats) and Allow header (listing permitted HTTP methods) based on resource operations
  • Integrated the processor into both Symfony and Laravel service configurations
  • Created comprehensive test coverage for the new functionality

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/State/Processor/LinkedDataPlatformProcessor.php Core processor implementation that inspects resource operations and sets LDP-compliant headers
src/Symfony/Bundle/Resources/config/symfony/events.php Symfony service configuration for the LinkedDataPlatformProcessor
src/Symfony/Bundle/Resources/config/state/processor.php Additional Symfony processor service definition with different decoration priority
src/Laravel/ApiPlatformProvider.php Laravel service provider integration for LinkedDataPlatformProcessor
tests/Functional/LinkDataPlatformTest.php Functional tests verifying headers in real HTTP responses
src/State/Tests/Processor/LinkedDataPlatformProcessorTest.php Unit tests for the processor logic
tests/Fixtures/TestBundle/ApiResource/DummyGetPostDeleteOperation.php Test fixture resource with specific operations for testing
src/State/Tests/Fixtures/ApiResource/Dummy.php Minimal test fixture for unit testing

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@soyuka soyuka requested a review from dunglas January 16, 2026 21:12
Copy link
Member

@dunglas dunglas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

I just wonder if adding new class is it worth it. Maybe this can be added to an existing base processor?

@soyuka soyuka force-pushed the feat/header-for-ldp-spec branch 3 times, most recently from 07bd710 to abdce50 Compare January 17, 2026 13:23
LaurentHuzard and others added 2 commits January 17, 2026 20:35
To ensure compliance with the LDP specification (https://www.w3.org/TR/ldp/):
Added the "Accept-Post" header containing the list of supported Post formats.
Added the "Allow" header with values based on the allowed operations on the queried resources.
@soyuka soyuka force-pushed the feat/header-for-ldp-spec branch from abdce50 to 5eca1f9 Compare January 17, 2026 19:35
@soyuka soyuka merged commit 350390b into api-platform:main Jan 17, 2026
149 checks passed
@soyuka
Copy link
Member

soyuka commented Jan 17, 2026

thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants